package com.blue.spring.redis;

import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * HttpSession的Redis实现
 * 
 * @author zhengj
 * @since 2016年5月17日 1.0
 */
public class RedisHttpSession implements HttpSession
{
	private static Logger logger = LoggerFactory.getLogger(RedisHttpSession.class);
	private static final int COOKIE_EXPIRE = 3600 * 24;

	private static final String CREATION_TIME = "CREATION_TIME";
	private static final String LAST_ACCESSED_TIME = "LAST_ACCESSED_TIME";
	private static final String ATTRIBUTE = "ATTRIBUTE:";
	private static final String ATTRIBUTE_LIST = "ATTRIBUTE_LIST";

	private ServletContext servletContext;
	private HttpServletRequest request;
	private HttpServletResponse response;
	private RedisTemplate<String, Object> redisTemplate;
	private String tokenName = "token";
	private int expire = 3600;
	private String key;

	public RedisHttpSession(ServletContext servletContext, HttpServletRequest request,
			HttpServletResponse response, RedisTemplate<String, Object> redisTemplate,
			String tokenName, int expire, String key)
	{
		this.servletContext = servletContext;
		this.request = request;
		this.response = response;
		this.redisTemplate = redisTemplate;
		this.tokenName = tokenName;
		this.expire = expire;
		this.key = key;
		
		BoundHashOperations<String, String, Object> hashOp = this.getHashOp(1);
		hashOp.put(CREATION_TIME, System.currentTimeMillis() / 1000);
	}

	@Override
	public Object getAttribute(String name)
	{
		BoundHashOperations<String, String, Object> hashOp = this.getHashOp(1);
		Object object = hashOp.get(ATTRIBUTE + name);
		return object;
	}

	@Override
	@SuppressWarnings("unchecked")
	public Enumeration<String> getAttributeNames()
	{
		BoundHashOperations<String, String, Object> hashOp = this.getHashOp(0);
		Set<String> nameSet = (Set<String>)hashOp.get(ATTRIBUTE_LIST);
		return Collections.enumeration(nameSet);
	}

	@Override
	public long getCreationTime()
	{
		BoundHashOperations<String, String, Object> hashOp = this.getHashOp(0);
		Long time = (Long)hashOp.get(CREATION_TIME);
		
		if (time == null)
			return 0;
		
		return time.longValue();
	}

	@Override
	public String getId()
	{
		return this.getAndSetToken(request, response);
	}

	@Override
	public long getLastAccessedTime()
	{
		BoundHashOperations<String, String, Object> hashOp = this.getHashOp(0);
		Long time = (Long)hashOp.get(LAST_ACCESSED_TIME);
		
		if (time == null)
			return 0;
		
		return time.longValue();
	}

	@Override
	public int getMaxInactiveInterval()
	{
		return expire;
	}

	@Override
	public ServletContext getServletContext()
	{
		return servletContext;
	}

	@Override
	@Deprecated
	public HttpSessionContext getSessionContext()
	{
		throw new UnsupportedOperationException("不支持该操作");
	}

	@Override
	@Deprecated
	public Object getValue(String name)
	{
		throw new UnsupportedOperationException("不支持该操作");
	}

	@Override
	@Deprecated
	public String[] getValueNames()
	{
		throw new UnsupportedOperationException("不支持该操作");
	}

	@Override
	public void invalidate()
	{
		String token = this.getAndSetToken(request, response);
		String key = this.key + token;
		
		redisTemplate.delete(key);
	}

	@Override
	public boolean isNew()
	{
		return false;
	}

	@Override
	@Deprecated
	public void putValue(String name, Object value)
	{
		throw new UnsupportedOperationException("不支持该操作");
	}

	@Override
	@SuppressWarnings("unchecked")
	public void removeAttribute(String name)
	{
		if (name == null || name.isEmpty())
			return;
		
		BoundHashOperations<String, String, Object> hashOp = this.getHashOp(1);
		hashOp.delete(ATTRIBUTE + name);
		
		Set<String> nameSet = (Set<String>)hashOp.get(ATTRIBUTE_LIST);
		if (nameSet != null)
		{
			nameSet.remove(name);
			hashOp.put(ATTRIBUTE_LIST, nameSet);
		}
	}

	@Override
	@Deprecated
	public void removeValue(String name)
	{
		throw new UnsupportedOperationException("不支持该操作");
	}

	@Override
	@SuppressWarnings("unchecked")
	public void setAttribute(String name, Object value)
	{
		if (value == null || "".equals(value))
		{
			this.removeAttribute(name);
			return;
		}
		
		BoundHashOperations<String, String, Object> hashOp = this.getHashOp(1);
		hashOp.put(ATTRIBUTE + name, value);
		
		Set<String> nameSet = (Set<String>)hashOp.get(ATTRIBUTE_LIST);
		if (nameSet == null)
			nameSet = new HashSet<>();
		nameSet.add(name);
		hashOp.put(ATTRIBUTE_LIST, nameSet);
	}

	@Override
	public void setMaxInactiveInterval(int interval)
	{
		this.expire = interval;
	}
	
	/**
	 * 获取redis操作入口
	 * 
	 * @param attr 0不访问attribute，1访问attribute，访问attribute需要改过期时间和访问时间
	 * @return
	 */
	private BoundHashOperations<String, String, Object> getHashOp(int attr)
	{
		String token = this.getAndSetToken(request, response);
		String key = this.key + token;
		BoundHashOperations<String, String, Object> hashOp = redisTemplate.boundHashOps(key);
		
		if (attr == 1)
		{
			//redisTemplate.expire(KEY_ATTR + token, expire, TimeUnit.SECONDS);
			hashOp.put(LAST_ACCESSED_TIME, System.currentTimeMillis() / 1000);
			redisTemplate.expire(key, expire, TimeUnit.SECONDS);
		}
		return hashOp;
	}
	
	/**
	 * 获取并设置令牌
	 */
	private String getAndSetToken(HttpServletRequest request, HttpServletResponse response)
	{
		String value = this.getToken(request);
		logger.debug("{}: {}", tokenName, value);
		if (value != null && !value.equals(""))
			return value;

		value = UUID.randomUUID().toString();
		Cookie cookie = new Cookie(tokenName, value);
		cookie.setMaxAge(COOKIE_EXPIRE);
		cookie.setPath("/");

		logger.debug("{}: {}", tokenName, value);
		response.addCookie(cookie);
		return value;
	}

	/**
	 * 获取令牌
	 * 
	 * @return
	 */
	private String getToken(HttpServletRequest request)
	{
		Cookie[] cookies = request.getCookies();
		if (cookies != null)
		{
			for (Cookie cookie : cookies)
			{
				if (tokenName.equals(cookie.getName()))
				{
					String value = cookie.getValue();
					logger.debug("{}: {}", tokenName, value);
					return value;
				}
			}
		}
		return null;
	}
	
	
}
